/*
 * Copyright (C) 2003-2006 the xine project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 * $Id: script_engine.c,v 1.84 2006/04/08 21:34:50 dsalt Exp $
 *
 * gate to the spider monkey javascript interpreter
 *
 * provides functions to generate interpreter instances
 * customized for controlling the xine engine and the gxine
 * frontend. this is done by defining a set of global objects
 * and functions so any xine and gxine function can be invoked from
 * (java)scripts.
 *
 */

#include "globals.h"

#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <pthread.h>
#include <stdarg.h>

#include <gdk/gdkkeysyms.h>
#include <jsstr.h>

#include "script_engine.h"
#include "ui.h"
#include "utils.h"
#include "mediamarks.h"
#include "playlist.h"
#include "settings.h"
#include "preferences.h"
#include "key_events.h"
#include "log_window.h"
#include "stream_info.h"
#include "open_mrl.h"
#include "server.h"
#include "wizards.h"
#include "snapshot.h"
#include "engine.h"
#include "history.h"
/*
#define LOG
*/
se_o_t *event_obj = NULL;

static se_o_t *js_icon_obj = NULL;

static JSRuntime *rt=NULL; /* global */

static GSList *se_chain;

static GtkListStore *se_error_store;
static GtkWidget *se_window, *eval_box;
static GtkTreeView *tree_view;

static const char *se_id (const se_t *se, const se_o_t *o, const char *prop)
{
  static char *id = NULL;

  free (id);
  id = prop ? strdup (prop) : NULL;
  while (o && o != se->g)
  {
    asreprintf (&id, "%s%s%s", o->id, id ? "." : "", id ? : "");
    o = o->parent;
  }
  return id ? : "[global]";
}

static void se_store_text (const char *type, const char *text)
{
  GtkTreeIter iter;
  GtkTreePath *path;
  gtk_list_store_append (GTK_LIST_STORE (se_error_store), &iter);
  gtk_list_store_set (GTK_LIST_STORE (se_error_store), &iter,
		      0, type, 1, text, -1);
  path = gtk_tree_model_get_path (GTK_TREE_MODEL (se_error_store), &iter);
  gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0, 1);
  gtk_tree_path_free (path);
}

static void se_js_icon (int flag)
{
  if (!GTK_WIDGET_VISIBLE (se_window))
  {
    int oldv = se_prop_get_int (gse, js_icon_obj, "v");
    int newv = oldv | flag;
    if (oldv != newv)
      se_prop_set_int (gse, js_icon_obj, "v", newv);
  }
}

void se_result_cb (void *data, const char *format, ...)
{
  va_list ap;
  char *msg, *text;

  va_start (ap, format);
  msg = g_strdup_vprintf (format, ap);
  va_end (ap);
  text = g_markup_printf_escaped ("%s", msg);
  free (msg);
  se_store_text (GTK_STOCK_DIALOG_INFO, text);
  free (text);
  se_js_icon (1);
}

void se_error_cb (const char *msg, const JSErrorReport *report)
{
  static const char *const type[] = {
    GTK_STOCK_DIALOG_ERROR, GTK_STOCK_DIALOG_WARNING,
    GTK_STOCK_DIALOG_ERROR, GTK_STOCK_DIALOG_QUESTION
  };
  static const char flag[] = { 8, 4, 8, 2 };

  char *message = g_markup_printf_escaped ("<i>%s</i>", msg);
  ssize_t l = strlen (message) - 4;
  while (--l >= 0 &&
	 (message[l] == ' ' || message[l] == '\n' || message[l] == '\t'))
    /**/;
  strcpy (message + l + 1, "</i>"); /* may have been chopped off */
  char *file = report->filename
	     ? g_markup_printf_escaped (_("<b>Source:</b> %s"), report->filename)
	     : NULL;
  char *line = report->lineno
	     ? g_markup_printf_escaped (_("<b>Line:</b> %d"), report->lineno)
	     : NULL;
  char *stmt = report->linebuf
	     ? g_markup_printf_escaped (_("<b>Line:</b> %s"), report->linebuf)
	     : NULL;

  char *text = g_strconcat (message, file ? "\n" : "", file ? : "",
			    line ? "\n" : "", line ? : "",
			    stmt ? "\n" : "", stmt ? : "", NULL);

  free (message);
  free (line);
  free (file);
  free (stmt);
  se_store_text (type[report->flags & 3], text);
  free (text);
  se_js_icon (flag[report->flags & 3]);
}

int se_eval_ext (se_t *se, const gchar *script, se_o_t *obj,
		 se_print_cb_t print_cb, void *print_cb_data,
		 se_error_cb_t error_cb, const char *src)
{
  pthread_mutex_lock (&se->lock);

  se_print_cb_t old_print_cb      = se->print_cb;
  void *        old_print_cb_data = se->print_cb_data;
  se_error_cb_t old_error_cb      = se->error_cb;
  se->print_cb      = print_cb;
  se->print_cb_data = print_cb_data;
  se->error_cb      = error_cb;

  int r = JS_EvaluateScript (se->cx, obj ? obj->obj : se->global, script,
			    strlen (script), src ? : "gxine", 0, &se->rval);

  se->print_cb      = old_print_cb;
  se->print_cb_data = old_print_cb_data;
  se->error_cb      = old_error_cb;

  pthread_mutex_unlock (&se->lock);
  return r;
}

int se_eval (se_t *se, const gchar *script, se_o_t *obj,
	     se_print_cb_t print_cb, void *print_cb_data, const char *src)
{
  return se_eval_ext (se, script, obj, print_cb, print_cb_data, se_error_cb,
		      src);
}

gchar *se_result_str (se_t *se)
{
  if (!JSVAL_IS_STRING (se->rval))
    return NULL;

  se->str = JS_ValueToString (se->cx, se->rval);
  return JS_GetStringBytes (se->str);
}

int se_result_int (se_t *se, JSInt32 *num)
{
  if (JSVAL_IS_INT (se->rval))
    return JS_ValueToInt32 (se->cx, se->rval, num);
  return 0;
}

int se_result_double (se_t *se, JSFloat64 *num)
{
  if (JSVAL_IS_DOUBLE (se->rval))
    return JS_ValueToNumber (se->cx, se->rval, num);
  return 0;
}

int se_result_bool (se_t *se, JSBool *num)
{
  if (JSVAL_IS_BOOLEAN (se->rval))
    return JS_ValueToBoolean (se->cx, se->rval, num);
  return 0;
}

int se_result_num_as_int (se_t *se, JSInt32 *num)
{
  if (JSVAL_IS_DOUBLE (se->rval))
  {
    JSFloat64 f;
    JS_ValueToNumber (se->cx, se->rval, &f);
    *num = floor (f);
    return 1;
  }
  return JS_ValueToInt32 (se->cx, se->rval, num);
}

int se_result_num_as_double (se_t *se, JSFloat64 *num)
{
  if (JSVAL_IS_INT (se->rval))
  {
    JSInt32 i;
    JS_ValueToInt32 (se->cx, se->rval, &i);
    *num = i;
    return 1;
  }
  return JS_ValueToNumber (se->cx, se->rval, num);
}

static inline jsval
se_js_string_val (JSContext *cx, const char *str)
{
  JSString *jstr = JS_NewStringCopyZ (cx, str);
  return STRING_TO_JSVAL (jstr);
}

/*
 * methods
 */

static JSBool controls_exit (JSContext *cx, JSObject *obj, uintN argc,
			     jsval *argv, jsval *rval)
{
  gchar *fname;

  se_log_fncall_checkinit ("controls.exit");

  xine_stop (stream);

  if (gtk_main_level() < 1)
  {
    settings_clear ();
    exit (2);
  }

  mm_save ();
  playlist_save (NULL);
  save_key_bindings ();
  save_startup_script ();

  logprintf ("script_engine: saving config...\n");

  fname = get_config_filename (FILE_CONFIG);
  xine_config_save (xine, fname);
  g_free (fname);

  fname = get_config_filename (FILE_ACCELS);
  gtk_accel_map_save (fname);
  g_free (fname);

  { /* lie about remembering the volume... */
    xine_cfg_entry_t entry;
    if (xine_config_lookup_entry (xine, "audio.volume.remember_volume", &entry))
    {
      entry.num_value = 0;
      xine_config_update_entry (xine, &entry);
    }
  }
  settings_clear ();

  gtk_main_quit();

  exit (0);
}

static JSBool show_js_console (JSContext *cx, JSObject *obj, uintN argc,
			       jsval *argv, jsval *rval)
{
  se_log_fncall_checkinit ("show_js_console");
  if (GTK_WIDGET_VISIBLE (se_window))
    gtk_widget_hide (se_window);
  else
  {
    window_show (se_window, NULL);
    se_prop_set_int (gse, js_icon_obj, "v", 0);
  }

  *rval = JSVAL_VOID;
  return JS_TRUE;
}

static JSBool
js_callback (JSContext *cx, JSObject *obj,
	     uintN argc, jsval *argv, jsval *rval)
{
  se_t *se = (se_t *) JS_GetContextPrivate (cx);
  se_log_fncall ("callback");
  se_argc_check_range (1, 2, "callback");
  se_arg_is_string (0, "callback");
  if (argc > 1)
    se_arg_is_object (1, "callback");
  engine_queue_push (JS_GetStringBytes (JS_ValueToString (cx, argv[0])),
		     JS_GetPrivate (cx, argc > 1 ? JSVAL_TO_OBJECT (argv[1])
						 : se->global),
		     NULL, NULL, NULL, _("JS callback"));
  *rval = JSVAL_VOID;
  return JS_TRUE;
}

static JSBool
js_xine_cfg_get (JSContext *cx, JSObject *obj,
		 uintN argc, jsval *argv, jsval *rval)
{
  se_log_fncall ("xine_cfg_get");
  se_argc_check (1, "xine_cfg_get");
  se_arg_is_string (0, "xine_cfg_get");

  xine_cfg_entry_t entry;
  char *cfg = JS_GetStringBytes (JS_ValueToString (cx, argv[0]));
  if (!xine_config_lookup_entry (xine, cfg, &entry))
  {
    *rval = JSVAL_NULL;
    return JS_TRUE;
  }

  switch (entry.type)
  {
  case XINE_CONFIG_TYPE_RANGE:
  case XINE_CONFIG_TYPE_NUM:
    *rval = INT_TO_JSVAL (entry.num_value);
    return JS_TRUE;

  case XINE_CONFIG_TYPE_BOOL:
    *rval = BOOLEAN_TO_JSVAL (entry.num_value);
    return JS_TRUE;

  case XINE_CONFIG_TYPE_STRING:
    *rval = se_js_string_val (cx, entry.str_value ? : entry.str_default);
    return JS_TRUE;

  case XINE_CONFIG_TYPE_ENUM:
    *rval = se_js_string_val (cx, entry.enum_values[entry.num_value]);
    return JS_TRUE;

  default:
    *rval = JSVAL_NULL;
    return JS_TRUE;
  }
}

static JSBool
js_xine_cfg_set (JSContext *cx, JSObject *obj,
		 uintN argc, jsval *argv, jsval *rval)
{
  se_log_fncall ("xine_cfg_set");
  se_argc_check (2, "xine_cfg_set");
  se_arg_is_string (0, "xine_cfg_set");

  xine_cfg_entry_t entry;
  char *cfg = JS_GetStringBytes (JS_ValueToString (cx, argv[0]));
  if (!xine_config_lookup_entry (xine, cfg, &entry))
  {
    *rval = JSVAL_VOID;
    return JS_TRUE;
  }

  int32 num;
  switch (entry.type)
  {
  case XINE_CONFIG_TYPE_RANGE:
    se_arg_is_int_or_bool (1, "xine_cfg_set");
    JS_ValueToInt32 (cx, argv[1], &num);
    entry.num_value = MIN (entry.range_max, MAX (entry.range_min, num));
    break;

  case XINE_CONFIG_TYPE_NUM:
    se_arg_is_int_or_bool (1, "xine_cfg_set");
    JS_ValueToInt32 (cx, argv[1], &num);
    entry.num_value = num;
    break;

  case XINE_CONFIG_TYPE_BOOL:
    se_arg_is_int_or_bool (1, "xine_cfg_set");
    JS_ValueToInt32 (cx, argv[1], &num);
    entry.num_value = !!num;
    break;

  case XINE_CONFIG_TYPE_STRING:
    se_arg_is_string (1, "xine_cfg_set");
    entry.str_value = JS_GetStringBytes (JS_ValueToString (cx, argv[1]));
    break;

  case XINE_CONFIG_TYPE_ENUM:
    se_arg_is_string (1, "xine_cfg_set");
    char *v = JS_GetStringBytes (JS_ValueToString (cx, argv[1]));
    int i;
    for (i = 0; entry.enum_values[i]; ++i)
      if (!strcmp (v, entry.enum_values[i]))
	break;
    if (entry.enum_values[i])
      entry.num_value = i;
    break;

  default:
    *rval = JSVAL_VOID;
    return JS_TRUE;
  }

  preferences_update_entry (&entry);

  *rval = JSVAL_VOID;
  return JS_TRUE;
}

static char *show_help_int (se_t *se, se_o_t *obj, se_group_t selector)
{
  const GList *objs;
  char *help = NULL;

  foreach_glist (objs, obj->functions)
  {
    se_f_t *cmd = objs->data;
    if (cmd->group == selector)
      asreprintf (&help, "%s    %s (%s)%s%s%s\n",
		  help ? : "", se_id (se, obj, cmd->id),
		  cmd->arghelp ? : "", cmd->funchelp ? "  /* " : "",
		  cmd->funchelp ? : "", cmd->funchelp ? " */" : "");
  }

  foreach_glist (objs, obj->children)
  {
    char *group = show_help_int (se, objs->data, selector);
    if (group)
    {
      asreprintf (&help, "%s%s", help ? : "", group);
      free (group);
    }
  }

  foreach_glist (objs, obj->children)
  {
    se_o_t *o = objs->data;
    if (o->group == selector)
      asreprintf (&help, "%s    %s\t%s%s%s\n",
		  help ? : "", se_id (se, obj, o->id),
		  o->help ? "  /* " : "", o->help ? : "",
		  o->help ? " */" : "");
  }

  return help;
}

static JSBool show_help (JSContext *cx, JSObject *obj, uintN argc,
			  jsval *argv, jsval *rval)
{
  static char *const group_id[] = {
    NULL,
    N_("Engine control:"),
    N_("Dialogue boxes:"),
    N_("Playlist control:"),
    N_("File access:"),
    N_("Status:"),
    N_("Properties:"),
    N_("Input event generation (DVD menus etc.):"),
    N_("External program support:"),
    NULL
  };

  int i;
  se_t *se = (se_t *) JS_GetContextPrivate(cx);

  se_log_fncall_checkinit ("help");

  se->print_cb (se->print_cb_data, "\n%s",
		_("Available commands (gxine Javascript interface):"));

  i = 0;
  while (group_id[++i])
  {
    char *help = NULL;
    const GSList *pse;

    foreach_glist (pse, se_chain)
    {
      char *group = show_help_int (se, ((se_t *)pse->data)->g, i);
      if (group)
      {
	asreprintf (&help, "%s%s  %s\n%s", help ? : "", help ? "\n" : "",
		    gettext (group_id[i]), group);
	free (group);
      }
    }

    if (!help)
      continue;
    help[strlen (help) - 1] = 0;
    se->print_cb (se->print_cb_data, "%s", help);
    free (help);
  }

  /* set_audio_channel (int);
   * set_spu_channel (int);
   * int get_pos ();
   * int get_time ();
   * int get_length ();
   */
  *rval = JSVAL_VOID;
  return JS_TRUE;
}

static int get_prop_int (se_t *se, se_o_t *o, se_prop_t *p)
{
  int n;

  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    return v.i;
  }

  if (sscanf (p->value, "%d", &n) == 1)
  {
    logprintf ("script_engine: this is a simple integer %d\n", n);
    return n;
  }
  if (!strncasecmp (p->value, "jscript:", 8))
  {
    char *script = p->value + 8;
    logprintf ("script_engine: this is a jscript => evaluating '%s'...\n",
               script);
    se_eval (se, script, o, NULL, NULL, "JS property");
    if (se_result_num_as_int (se, &n))
    {
      logprintf ("script_engine: result: %d\n", n);
      return n;
    }
    g_printerr (_("script_engine: error: non-numeric result in %s==‘%s’\n"),
	     p->id, p->value);
  } else
    g_printerr (_("script_engine: error: non-numeric property %s==‘%s’\n"),
	     p->id, p->value);

  return 0;
}

static double get_prop_double (se_t *se, se_o_t *o, se_prop_t *p)
{
  double n;

  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    return v.d;
  }

  if (sscanf (p->value, "%lf", &n) == 1)
  {
    logprintf ("script_engine: this is a simple float %lf\n", n);
    return n;
  }
  if (!strncasecmp (p->value, "jscript:", 8))
  {
    char *script = p->value + 8;
    logprintf ("script_engine: this is a jscript => evaluating '%s'...\n",
               script);
    se_eval (se, script, o, NULL, NULL, "JS property");
    if (se_result_num_as_double (se, &n))
    {
      logprintf ("script_engine: result: %lf\n", n);
      return n;
    }
    g_printerr (_("script_engine: error: non-numeric result in %s==‘%s’\n"),
	     p->id, p->value);
  } else
    g_printerr (_("script_engine: error: non-numeric property %s==‘%s’\n"),
	     p->id, p->value);

  return 0;
}

static JSBool get_prop_bool (se_t *se, se_o_t *o, se_prop_t *p)
{
  int n;

  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    return v.i ? JS_TRUE : JS_FALSE;
  }

  if (sscanf (p->value, "%d", &n) == 1)
  {
    n = !!n;
    logprintf ("script_engine: this is a simple boolean %d\n", n);
    return n ? JS_TRUE : JS_FALSE;
  }
  return strcasecmp (p->value, "false") ? JS_TRUE : JS_FALSE;
}

static const char *get_prop_string (se_t *se, se_o_t *o, se_prop_t *p)
{
  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    return v.s;
  }

  return p->value;
}

static jsval get_prop_jsval (se_t *se, se_o_t *o, se_prop_t *p)
{
  logprintf ("script_engine: getting value of %s=='%s'\n", p->id, p->value);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    {
      int n = get_prop_int (se, o, p);
      return INT_TO_JSVAL (n);
    }

  case SE_TYPE_DOUBLE:
    {
      double d = get_prop_int (se, o, p);
      return DOUBLE_TO_JSVAL (d);
    }

  case SE_TYPE_BOOL:
    {
      JSBool b = get_prop_bool (se, o, p);
      return BOOLEAN_TO_JSVAL (b);
    }

  case SE_TYPE_STRING:
    {
      const char *s = get_prop_string (se, o, p);
      return se_js_string_val (se->cx, s);
    }

  default:
    g_printerr (_("script_engine: error: property %s has unknown type %d\n"),
	     p->id, p->se_type);
  }

  return BOOLEAN_TO_JSVAL (0);
}

static void set_prop_jsval (se_t *se, se_o_t *o, se_prop_t *p, const jsval *v)
{
  logprintf ("script_engine: setting value of %s\n", p->id);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    if (JSVAL_IS_NUMBER(*v))
      se_prop_set_int (se, o, p->id, JSVAL_TO_INT(*v));
    else
      se->print_cb (se->print_cb_data, _("\n%s.%s: value must be numeric\n"),
		    o->id, p->id);
    return;

  case SE_TYPE_DOUBLE:
    if (JSVAL_IS_NUMBER(*v))
      se_prop_set_double (se, o, p->id, *JSVAL_TO_DOUBLE(*v));
    else
      se->print_cb (se->print_cb_data, _("\n%s.%s: value must be numeric\n"),
		    o->id, p->id);
    return;

  case SE_TYPE_BOOL:
    if (JSVAL_IS_BOOLEAN(*v))
      se_prop_set_bool (se, o, p->id, JSVAL_TO_BOOLEAN(*v));
    else
      se->print_cb (se->print_cb_data, _("\n%s.%s: value must be boolean\n"),
		    o->id, p->id);
    return;

  case SE_TYPE_STRING:
    {
      JSString *str = JSVAL_TO_STRING(*v);
      char     *string = JS_GetStringBytes (str);
      se_prop_set (se, o, p->id, string);
    }
    return;

  default:
    g_printerr (_("script_engine: error: property %s has unknown type %d\n"),
	     p->id, p->se_type);
  }
}


/*
 * function to create and maintain js objects
 */

static JSBool
generic_JSGetProperty (JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
  se_t   *se = (se_t *) JS_GetContextPrivate(cx);
  se_log_fncall ("generic get property");
  se_o_t *o = JS_GetPrivate (cx, obj);

  if (JSVAL_IS_STRING (id))
  {
    JSString *str = JS_ValueToString (cx, id);
    char     *prop = str ? JS_GetStringBytes (str) : "";
    GList    *n;

    logprintf ("script_engine: looking for generic property '%s' in '%s'\n",
	       prop, o->id);

    /* look through properties */

    foreach_glist (n, o->properties)
    {
      se_prop_t *p = (se_prop_t *) n->data;
      if (!strcasecmp (p->id, prop))
      {
        *vp = get_prop_jsval (se, o, p);
        return JS_TRUE;
      }
    }

    /* look through objects for default value */

    foreach_glist (n, o->children)
    {
      se_o_t *p = (se_o_t *) n->data;
      if (!strcasecmp (p->id, prop))
      {
	static jsval prop = 0;
	if (!prop)
	  prop = se_js_string_val (cx, ".");
	if (!generic_JSGetProperty (cx, p->obj, prop, vp))
	  *vp = OBJECT_TO_JSVAL (p->obj);
	return JS_TRUE;
      }
    }

    if (*prop && o->parent)
      return generic_JSGetProperty (cx, o->parent->obj, id, vp);

    return JS_TRUE;
  }

  *vp = JSVAL_VOID;

  return JS_FALSE;
}

static JSBool
generic_JSSetProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
  se_t   *se = (se_t *) JS_GetContextPrivate(cx);
  se_o_t *o;

  se_log_fncall ("generic set property");

  o = JS_GetPrivate (cx, obj);

  if (JSVAL_IS_STRING (id))
  {
    JSString *str = JS_ValueToString (cx, id);
    char     *prop = JS_GetStringBytes (str);
    GList    *n;

    logprintf ("script_engine: looking for generic property '%s' in '%s'\n",
	       prop, o->id);

    /* look through properties */

    foreach_glist (n, o->properties)
    {
      se_prop_t *p = (se_prop_t *) n->data;
      if (!strcasecmp (p->id, prop))
      {
	if (!p->constant)
	  set_prop_jsval (se, o, p, vp);
        return JS_TRUE;
      }
    }

    /* look through objects for default value */

    foreach_glist (n, o->children)
    {
      se_o_t *p = (se_o_t *) n->data;
      if (!strcasecmp (p->id, prop))
      {
	if (!generic_JSSetProperty (cx, p->obj, STRING_TO_JSVAL("."), vp))
	  *vp = OBJECT_TO_JSVAL (p->obj);
	return JS_TRUE;
      }
    }

    if (*prop && o->parent)
      return generic_JSSetProperty (cx, o->parent->obj, id, vp);

    return JS_TRUE;
  }

  *vp = JSVAL_VOID;

  return JS_FALSE;
}

static void generic_JSDestructor (JSContext *cx, JSObject *obj)
{
  se_log_fncall ("generic destructor");
}

static JSClass generic_JSClass =
  {
    "view", JSCLASS_HAS_PRIVATE,
    JS_PropertyStub, JS_PropertyStub,
    generic_JSGetProperty, generic_JSSetProperty,
    JS_EnumerateStub, JS_ResolveStub,
    JS_ConvertStub, generic_JSDestructor
  };

se_o_t *se_create_object (se_t *se, se_o_t *parent /* may be NULL */,
			  const gchar *name, void *user_data,
			  se_group_t group, const gchar *help)
{
  se_o_t *o;

  logprintf ("script_engine: creating object %s parent: %s\n",
	     name, se_id (se, parent, NULL));

  o = (se_o_t *) malloc (sizeof (se_o_t));

  if (!parent)
    o->obj = JS_DefineObject (se->cx, se->global, name,
			      &generic_JSClass, NULL, 0);
  else
    o->obj = JS_DefineObject (se->cx, parent->obj, name,
			      &generic_JSClass, NULL, 0);

  o->user_data   = user_data;
  o->parent      = parent;
  o->id          = strdup (name);
  o->children    = NULL;
  o->properties  = NULL;
  o->functions   = NULL;
  o->group	 = group;
  o->help	 = help;

  JS_SetPrivate (se->cx, o->obj, o);

  if (parent)
    parent->children = g_list_append (parent->children, o);
  else
    se->g->children = g_list_append (se->g->children, o);

  return o;
}

se_o_t *se_find_object (se_t *se, se_o_t *parent, const char *name)
{
  GList *objs;
  while (strchr (name, '.'))
  {
    int index = strchr (name, '.') - name;
    foreach_glist (objs, parent ? parent->children : se->g->children)
    {
      se_o_t *o = objs->data;
      if (!strncasecmp (o->id, name, index) && !o->id[index])
      {
        name += index + 1;
        parent = o;
        break;
      }
    }
    if (!objs)
      return NULL;
  }
  foreach_glist (objs, parent ? parent->children : se->g->children)
  {
    se_o_t *o = objs->data;
    if (!strcasecmp (o->id, name))
      return o;
  }
  return NULL;
}

static se_o_t dflt = {};

se_o_t *se_get_object (se_t *se, se_o_t *parent, const char *name)
{
  se_o_t *o = se_find_object (se, parent, name);
  if (o)
    return o;
  free (dflt.id);
  dflt.id = strdup (name);
  return &dflt;
}

static se_prop_t *se_prop_find (se_t *se, se_o_t *o, const char *id)
{
  GList *node;
  foreach_glist (node, o ? o->properties : se->g->properties)
  {
    se_prop_t *p = node->data;
    if (!strcasecmp (p->id, id))
      return p;
  }
  return NULL;
}

static void se_prop_listeners (se_t *se, se_o_t *o, se_prop_t *p, se_prop_read_t r)
{
  GList *node;
  foreach_glist (node, p->listeners)
  {
    const se_prop_listener_t *l = node->data;
    l->cb (l->user_data, se, o, p, r);
  }
}

void se_prop_set (se_t *se, se_o_t *o, const char *id, const char *value)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  p = se_prop_find (se, o, id);
  if (p)
  {
    se_prop_read_t v;
    if (p->reader && p->se_type != SE_TYPE_STRING)
    {
      g_printerr (_("script_engine: se_prop_set%s: can't change type of property %s\n"),
	       "", se_id (se, o, id));
      return;
    }
    free (p->value);
    v.s = p->value = strdup (value);
    p->se_type = SE_TYPE_STRING;
    se_prop_listeners (se, o, p, v);
  }
  else
    se_prop_create (se, o, id, value, NULL, 0, SE_TYPE_STRING, FALSE);
}

void se_prop_set_int (se_t *se, se_o_t *o, const char *id, int value)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  p = se_prop_find (se, o, id);
  if (p)
  {
    se_prop_read_t v;
    if (p->reader && p->se_type != SE_TYPE_INT)
    {
      g_printerr (_("script_engine: se_prop_set%s: can't change type of property %s\n"),
	       "_int", se_id (se, o, id));
      return;
    }
    free (p->value);
    p->value = g_strdup_printf ("%d", v.i = value);
    p->se_type = SE_TYPE_INT;
    se_prop_listeners (se, o, p, v);
  }
  else
    se_prop_create_int (se, o, id, value, FALSE);
}

void se_prop_set_double (se_t *se, se_o_t *o, const char *id, double value)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  p = se_prop_find (se, o, id);
  if (p)
  {
    se_prop_read_t v;
    if (p->reader && p->se_type != SE_TYPE_DOUBLE)
    {
      g_printerr (_("script_engine: se_prop_set%s: can't change type of property %s\n"),
	      "_double", se_id (se, o, id));
      return;
    }
    free (p->value);
    p->value = g_strdup_printf ("%la", v.d = value);
    p->se_type = SE_TYPE_DOUBLE;
    se_prop_listeners (se, o, p, v);
  }
  else
    se_prop_create_double (se, o, id, value, FALSE);
}

void se_prop_set_bool (se_t *se, se_o_t *o, const char *id, int value)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  p = se_prop_find (se, o, id);
  if (p)
  {
    se_prop_read_t v;
    if (p->reader && p->se_type != SE_TYPE_BOOL)
    {
      g_printerr (_("script_engine: se_prop_set%s: can't change type of property %s\n"),
	       "_bool", se_id (se, o, id));
      return;
    }
    free (p->value);
    p->value = g_strdup_printf ("%d", v.i = !!value);
    p->se_type = SE_TYPE_BOOL;
    se_prop_listeners (se, o, p, v);
  }
  else
    se_prop_create_bool (se, o, id, value, FALSE);
}

char *se_prop_get (se_t *se, se_o_t *o, const char *id)
{
  if (!o)
    o = se->g;

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    g_printerr (_("script_engine: se_prop_get%s error: property %s doesn't exist\n"),
	     "", se_id (se, o, id));
    return NULL;
  }

  if (p->reader)
  {
    se_prop_read_t v;
    p->reader (p, &v);
    switch (p->se_type)
    {
    case SE_TYPE_INT:
      free (p->value);
      return p->value = g_strdup_printf ("%d", v.i);
    case SE_TYPE_BOOL:
      free (p->value);
      return p->value = strdup (v.i ? "true" : "false");
    case SE_TYPE_DOUBLE:
      free (p->value);
      return p->value = g_strdup_printf ("%la", v.d);
    case SE_TYPE_STRING:
      free (p->value);
      return p->value = strdup (v.s);
    default:;
    }
  }

  return p->value;
}

int se_prop_get_int (se_t *se, se_o_t *o, const char *id)
{
  if (!o)
    o = se->g;

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    g_printerr (_("script_engine: se_prop_get%s error: property %s doesn't exist\n"),
	     "_int", se_id (se, o, id));
    return 0;
  }

  logprintf ("script_engine: getting value of %s=='%s'\n",
	     se_id (se, o, id), p->value);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    return get_prop_int (se, o, p);

  case SE_TYPE_DOUBLE:
    return floor (get_prop_double (se, o, p));

  case SE_TYPE_BOOL:
    return get_prop_bool (se, o, p);

  default:
    g_printerr (_("script_engine: se_prop_get%s error: %s is not numeric\n"),
	     "_int", se_id (se, o, id));
  }
  return 0;
}

double se_prop_get_double (se_t *se, se_o_t *o, const char *id)
{
  if (!o)
    o = se->g;

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    g_printerr (_("script_engine: se_prop_get%s error: property %s doesn't exist\n"),
	     "_double", se_id (se, o, id));
    return 0;
  }

  logprintf ("script_engine: getting value of %s=='%s'\n",
	     se_id (se, o, id), p->value);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    return 0.0 + get_prop_int (se, o, p);

  case SE_TYPE_DOUBLE:
    return get_prop_double (se, o, p);

  case SE_TYPE_BOOL:
    return 0.0 + get_prop_bool (se, o, p);

  default:
    g_printerr (_("script_engine: se_prop_get%s error: %s is not numeric\n"),
	     "_double", se_id (se, o, id));
  }
  return 0;
}

JSBool se_prop_get_bool (se_t *se, se_o_t *o, const char *id)
{
  if (!o)
    o = se->g;

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    g_printerr (_("script_engine: se_prop_get%s error: property %s doesn't exist\n"),
	     "_bool", se_id (se, o, id));
    return JS_FALSE;
  }

  logprintf ("script_engine: getting value of %s=='%s'\n",
	     se_id (se, o, id), p->value);

  switch (p->se_type)
  {
  case SE_TYPE_INT:
    return get_prop_int (se, o, p) ? JS_TRUE : JS_FALSE;

  case SE_TYPE_DOUBLE:
    return (get_prop_double (se, o, p) != 0) ? JS_TRUE : JS_FALSE;

  case SE_TYPE_BOOL:
    return get_prop_bool (se, o, p);

  default:
    g_printerr (_("script_engine: se_prop_get%s error: %s is not numeric\n"),
	     "_bool", se_id (se, o, id));
  }
  return JS_FALSE;
}

void se_prop_create (se_t *se, se_o_t *o,
                     const char *id, const char *value,
		     const char *xine_id, int xine_param,
		     se_prop_type_t se_type, gboolean constant)
{
  if (!o)
    o = se->g;

  logprintf ("script_engine: setting %s to '%s'\n", se_id (se, o, id), value);

  se_prop_t *p = se_prop_find (se, o, id);
  if (!p)
  {
    logprintf ("script_engine: creating new property %s\n", se_id (se, o, id));
    p = malloc (sizeof (se_prop_t));
    p->id = strdup (id);
    p->value = strdup (value);
    p->xine_id = xine_id ? strdup (xine_id) : NULL;
    p->xine_param = xine_param;
    p->reader = NULL;
    p->listeners = NULL;
    o->properties = g_list_append (o->properties, p);
  }

  p->se_type = se_type;
  p->constant = constant;
}

void se_prop_create_int (se_t *se, se_o_t *o, const char *id, int value,
			 gboolean constant)
{
  char str[24];
  snprintf (str, sizeof (str), "%d", value);
  se_prop_create (se, o, id, str, NULL, 0, SE_TYPE_INT, constant);
}

void se_prop_create_double (se_t *se, se_o_t *o, const char *id, double value,
			    gboolean constant)
{
  char str[24];
  snprintf (str, sizeof (str), "%la", value); /* C99 hex float */
  se_prop_create (se, o, id, str, NULL, 0, SE_TYPE_DOUBLE, constant);
}

void se_prop_create_bool (se_t *se, se_o_t *o, const char *id, int value,
			  gboolean constant)
{
  char str[24];
  snprintf (str, sizeof (str), "%d", !!value);
  se_prop_create (se, o, id, str, NULL, 0, SE_TYPE_BOOL, constant);
}

static void se_prop_read_xine_id_int (se_prop_t *prop, se_prop_read_t *value)
{
  xine_cfg_entry_t entry;
  if (xine_config_lookup_entry (xine, prop->xine_id, &entry))
    value->i = entry.num_value;
  else
    value->i = 0;
}

static void se_prop_read_xine_id_string (se_prop_t *prop, se_prop_read_t *value)
{
  xine_cfg_entry_t entry;
  if (xine_config_lookup_entry (xine, prop->xine_id, &entry))
    value->s = entry.str_value;
  else
    value->s = "";
}

void se_prop_create_xine_id (se_t *se, se_o_t *o,
			     const char *id, const char *xine_id)
{
  xine_cfg_entry_t entry = { NULL };
  xine_config_lookup_entry (xine, xine_id, &entry);
  switch (entry.type)
  {
  case XINE_CONFIG_TYPE_RANGE:
  case XINE_CONFIG_TYPE_NUM:
    se_prop_create (se, o, id, ".", xine_id, 0, SE_TYPE_INT, FALSE);
    se_prop_set_reader_func (se, o, id, se_prop_read_xine_id_int);
    return;
  case XINE_CONFIG_TYPE_ENUM:
    g_printerr (_("script_engine: can't create property for xine config enum type\n"));
    return;
  case XINE_CONFIG_TYPE_STRING:
    se_prop_create (se, o, id, "", xine_id, 0, SE_TYPE_STRING, FALSE);
    se_prop_set_reader_func (se, o, id, se_prop_read_xine_id_string);
    return;
  case XINE_CONFIG_TYPE_BOOL:
    se_prop_create (se, o, id, "", xine_id, 0, SE_TYPE_BOOL, FALSE);
    se_prop_set_reader_func (se, o, id, se_prop_read_xine_id_int);
    return;
  default:
    g_printerr (_("script_engine: can't create property for xine config unknown type\n"));
  }
}

static void se_prop_read_xine_param (se_prop_t *prop, se_prop_read_t *value)
{
  value->i = xine_get_param (stream, prop->xine_param);
}

void se_prop_create_xine_param (se_t *se, se_o_t *o,
			        const char *id, int xine_param,
				se_prop_type_t type)
{
  switch (type)
  {
  case SE_TYPE_BOOL:
  case SE_TYPE_INT:
    se_prop_create (se, o, id, "", NULL, xine_param, type, FALSE);
    se_prop_set_reader_func (se, o, id, se_prop_read_xine_param);
    return;
  default:
    g_printerr (_("script_engine: can't create property for xine param non-int type\n"));
  }
}

void se_prop_add_listener (se_t *se, se_o_t *o, const char *id,
			   se_prop_cb_t prop_cb, void *user_data)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  logprintf ("script_engine: adding listener to %s\n", se_id (se, o, id));

  p = (o == &dflt) ? NULL : se_prop_find (se, o, id);
  if (!p)
    g_printerr (_("script_engine: error in %s: property %s not found\n"),
	     __func__, se_id (se, o, id));
  else
  {
    se_prop_listener_t *listener = malloc (sizeof (se_prop_listener_t));
    listener->cb = prop_cb;
    listener->user_data = user_data;
    p->listeners = g_list_append (p->listeners, listener);
  }
}

void se_prop_set_reader_func (se_t *se, se_o_t *o, const char *id,
			      se_prop_reader_t reader)
{
  se_prop_t *p;

  if (!o)
    o = se->g;

  logprintf ("script_engine: setting reader for %s\n", se_id (se, o, id));

  p = se_prop_find (se, o, id);
  if (!p)
    g_printerr (_("script_engine: error in %s: property %s not found\n"),
	     __func__, se_id (se, o, id));
  else
    p->reader = reader;
}

static void error_reporter (JSContext *cx, const char *message,
			    JSErrorReport *report)
{
  se_t *se = (se_t *) JS_GetContextPrivate(cx);
  if (se->error_cb)
    se->error_cb (message, report);
  if (se->print_cb && se->print_cb != se_result_cb)
    se->print_cb (se->print_cb_data, "%s", message);
  logprintf ("script_engine: JSError '%s'\n", message);
}

se_f_t *se_defun (se_t *se, se_o_t *o /* may be NULL */,
                  const char *name, JSNative fun, uintN nargs, uintN attrs,
                  se_group_t group, const char *arghelp, const char *funchelp)
{
  se_f_t *f;
  f = malloc (sizeof(se_f_t));
  f->id = strdup (name);

  if (o)
  {
    f->fun = JS_DefineFunction (se->cx, o->obj, name, fun, nargs, attrs);
    o->functions = g_list_append (o->functions, f);
  }
  else
  {
    f->fun = JS_DefineFunction (se->cx, se->global, name, fun, nargs, attrs);
    se->g->functions = g_list_append (se->g->functions, f);
  }

  f->group = group;
  f->arghelp = arghelp ? gettext (arghelp) : NULL;
  f->funchelp = funchelp ? gettext (funchelp) : NULL;

  return f;
}

void se_defuns (se_t *se, se_o_t *o /* may be NULL */, const se_f_def_t defs[])
{
  int i = -1;
  while (defs[++i].name)
    se_defun (se, o, defs[i].name, defs[i].func, defs[i].nargs, defs[i].attrs,
	      defs[i].group, defs[i].arghelp, defs[i].funchelp);
}

static void se_execute_cb (GtkWidget *widget, char *js)
{
  engine_exec (js, se_result_cb, NULL, _("Javascript console"));
}

static void se_response_cb (GtkWidget *widget, int response, gpointer data)
{
  switch (response)
  {
  case GTK_RESPONSE_REJECT:
    gtk_list_store_clear (GTK_LIST_STORE (se_error_store));
    break;
  default:
    gtk_widget_hide (se_window);
  }
}

static gboolean
console_scroll_cb (GtkWidget *w, GdkEventKey *e, GtkWidget *t)
{
  GtkAdjustment *adj = gtk_tree_view_get_vadjustment (tree_view);
  gdouble value = adj->value;

  switch (e->state & GXINE_MODIFIER_MASK)
  {
  default:
    return FALSE;

  case 0:
    switch (e->keyval)
    {
    default:		return FALSE;
    case GDK_Page_Up:	value -= adj->page_increment; break;
    case GDK_Page_Down:	value += adj->page_increment; break;
    }
    break;

  case GDK_SHIFT_MASK:
    switch (e->keyval)
    {
    default:		return FALSE;
    case GDK_Up:	value -= adj->step_increment; break;
    case GDK_Down:	value += adj->step_increment; break;
    case GDK_Page_Up:	value -= adj->page_increment; break;
    case GDK_Page_Down:	value += adj->page_increment; break;
    }
    break;

  case GDK_CONTROL_MASK:
    switch (e->keyval)
    {
    default:		return FALSE;
    case GDK_Page_Up:	value = adj->lower; break;
    case GDK_Page_Down:	value = adj->upper; break;
    }
    break;
  }

  if (value < adj->lower)
    value = adj->lower;
  else if (value > adj->upper - adj->page_size)
    value = adj->upper - adj->page_size;

  gtk_adjustment_set_value (adj, value);
  return TRUE;
}

static void
se_create_console (void)
{
  GtkWidget *w, *b;
  GtkCellRenderer *cell;
  GtkTreeViewColumn *column;

  se_window = gtk_dialog_new_with_buttons (_("Javascript console"), NULL, 0,
				GTK_STOCK_CLEAR, GTK_RESPONSE_REJECT,
				GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
				NULL);
  gtk_window_set_default_size (GTK_WINDOW (se_window), 480, 250);
  g_object_connect (se_window,
	"signal::delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL,
	"signal::response", G_CALLBACK (se_response_cb), NULL,
	NULL);

  w = gtk_hbox_new (FALSE, 2);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (se_window)->vbox), w,
		      FALSE, FALSE, 2);

  eval_box = history_combo_box_new (se_execute_cb);
  gtk_box_pack_start_defaults (GTK_BOX (w), eval_box);

  b = gtk_button_new_from_stock (GTK_STOCK_EXECUTE);
  gtk_box_pack_start (GTK_BOX (w), b, FALSE, FALSE, 0);
  history_combo_box_set_activate_button (eval_box, b);

  b = ui_button_new_stock_mnemonic (GTK_STOCK_CLEAR, _("_Flush"));
  gtk_box_pack_start (GTK_BOX (w), b, FALSE, FALSE, 0);
  g_signal_connect_swapped (b, "clicked",
			    G_CALLBACK (history_combo_box_clear), eval_box);

  se_error_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
  tree_view = GTK_TREE_VIEW (gtk_tree_view_new_with_model
			       (GTK_TREE_MODEL (se_error_store)));
  gtk_tree_view_set_rules_hint (tree_view, TRUE);
  gtk_tree_view_set_reorderable (tree_view, FALSE);
  gtk_tree_view_set_headers_visible (tree_view, FALSE);

  cell = gtk_cell_renderer_pixbuf_new ();
  g_object_set (cell, "stock-size", (guint) GTK_ICON_SIZE_DIALOG, NULL);
  column = gtk_tree_view_column_new_with_attributes
	     ("", cell, "stock-id", 0, NULL);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_tree_view_append_column (tree_view, column);

  cell = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes
	     ("", cell, "markup", 1, NULL);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_tree_view_append_column (tree_view, column);

  w = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w),
				  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_container_add (GTK_CONTAINER (w), GTK_WIDGET (tree_view));

  g_signal_connect (G_OBJECT (eval_box), "key-press-event",
		    G_CALLBACK (console_scroll_cb), w);

  gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (se_window)->vbox), w);
}

se_t *se_new (void)
{
  se_t    *se;
  static JSClass global_class = {
    "global", JSCLASS_HAS_PRIVATE,
    JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,JS_PropertyStub,
    JS_EnumerateStub,JS_ResolveStub,JS_ConvertStub,JS_FinalizeStub
  };

  /*
   * create the JS console window
   */

  if (!se_window)
    se_create_console ();

  /*
   * create a js engine instance
   */

  if (!rt)
    rt = JS_NewRuntime(0x100000);

  se = malloc (sizeof (se_t));

  se->cx       = JS_NewContext (rt, 0x1000);
  se->global   = JS_NewObject (se->cx, &global_class, NULL, NULL);

  pthread_mutexattr_t attr;
  pthread_mutexattr_init (&attr);
#ifdef PTHREAD_MUTEX_RECURSIVE_NP
  pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE_NP);
#else /* *BSD does not have the _NP in the name */
  pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
#endif
  pthread_mutex_init (&se->lock, &attr);
  pthread_mutexattr_destroy (&attr);

  JS_InitStandardClasses (se->cx, se->global);
  JS_SetErrorReporter (se->cx, error_reporter);
  JS_SetContextPrivate (se->cx, se);

  /*
   * create fake global se_o
   */

  se->g = malloc (sizeof(se_o_t));
  se->g->obj        = se->global;
  se->g->parent     = NULL;
  se->g->id         = strdup("_global_");
  se->g->children   = NULL;
  se->g->functions  = NULL;
  se->g->properties = NULL;

  se_chain = g_slist_append (se_chain, se);

  {
    static const se_f_def_t defs[] = {
      { "exit", controls_exit, 0, 0, SE_GROUP_ENGINE, NULL, NULL },
      { "js_console_show", show_js_console, 0, 0, SE_GROUP_DIALOGUE, NULL, NULL },
      { "help", show_help, 0, 0, SE_GROUP_HIDDEN, NULL, NULL },
      { "callback", js_callback, 0, 0, SE_GROUP_ENGINE, NULL, NULL },
      { "xine_cfg_get", js_xine_cfg_get, 0, 0, SE_GROUP_PROPERTIES, NULL, NULL },
      { "xine_cfg_set", js_xine_cfg_set, 0, 0, SE_GROUP_PROPERTIES, NULL, NULL },
      { NULL }
    };
    se_defuns (se, se->g, defs);
  }

  event_obj = se_create_object (se, NULL, "event", NULL, SE_GROUP_ENGINE,
				"stream_end=JS");
  se_prop_create (se, event_obj, "stream_end", "", NULL, 0,
		  SE_TYPE_STRING, FALSE);

  js_icon_obj = se_create_object (se, NULL, "__js", NULL, SE_GROUP_HIDDEN, NULL);
  se_prop_create_int (se, js_icon_obj, "v", 0, TRUE);

  return se;
}

JSBool se_warn_initialisation (JSContext *cx, const char *func)
{
  JS_ReportWarning
    (cx, _("Attempt to call function “%s ()” during initialisation"), func);
  return JS_FALSE;
}
